import { MqttClient, IClientOptions, ISubscriptionGrant, PacketCallback } from "mqtt"
import * as mqtt from "mqtt/dist/mqtt.min.js"

interface MqttMessage {
  msg: any;
  topic: string;
}

type ConnectionStatusHandler = (isConnected: boolean) => void;
type MessageCallback = (message: MqttMessage) => void;

interface IMqttHandler {
  reconnect(): void;
  init(callback?: MessageCallback): void;
  subscribe(topics: string | string[]): Promise<void>;
  unsubscribe(topics: string | string[]): Promise<void>;
  end(): void;
  isConnected(): boolean;
  publish(topic: string, msg: any): Promise<void>;
}

class MqttHandler implements IMqttHandler {
  // eslint-disable-next-line no-use-before-define
  private static instance: MqttHandler
  private client: MqttClient | null = null
  private subscribedTopics: Set<string> = new Set()
  private handleEnd: ConnectionStatusHandler
  private callback: MessageCallback | null = null

  public static getInstance(callback: ConnectionStatusHandler): MqttHandler {
    if (!MqttHandler.instance) {
      MqttHandler.instance = new MqttHandler(callback)
    }
    return MqttHandler.instance
  }

  private constructor(callback: ConnectionStatusHandler) {
    this.handleEnd = callback
  }

  public reconnect(): void {
    if (this.client) {
      this.client.end(true, () => this.init())
    }
  }

  private generateClientId(prefix: string = "web_"): string {
    return `${prefix}${Math.random().toString(36).substring(2, 10)}`
  }

  public async init(callback?: MessageCallback): Promise<void> {
    if (callback) {
      this.callback = callback
    }
    this.end()

    const options: IClientOptions = {
      clientId: this.generateClientId(),
      clean: false,
      username: "test",
      password: "passwd",
      keepalive: 60,
      reconnectPeriod: 2000,
      connectTimeout: 4000,
      protocol: import.meta.env.VITE_APP_MQTT_PROTOCOL // 明确协议
    }

    this.client = mqtt.connect(import.meta.env.VITE_APP_MQTT_URL as string, options)
    this.setupEventHandlers()

    // 自动重新订阅已订阅的topic
    if (this.subscribedTopics.size > 0) {
      await this.subscribe([...this.subscribedTopics])
    }
  }

  private setupEventHandlers(): void {
    if (!this.client) { return }

    this.client.on("connect", (e) => {
      this.handleEnd(true)
      console.log("MQTT connected", e)
    })

    this.client.on("message", (topic: string, message: any) => {
      try {
        const msg = JSON.parse(message.toString())
        this.callback?.({ msg, topic })
      } catch (err) {
        console.error("Invalid MQTT message", err)
      }
    })

    const handleDisconnect = (event: string) => (err: any) => {
      this.handleEnd(false)
      console.warn(`MQTT ${event}`, err)
    }

    this.client.on("disconnect", handleDisconnect("disconnect"))
    this.client.on("offline", handleDisconnect("offline"))
    this.client.on("close", handleDisconnect("close"))
    this.client.on("end", handleDisconnect("end"))
    this.client.on("error", handleDisconnect("error"))
  }

  public async subscribe(topics: string | string[]): Promise<void> {
    if (!this.client) { throw new Error("MQTT client not initialized") }

    const topicArray = Array.isArray(topics) ? topics : [topics]
    const newTopics = topicArray.filter(t => !this.subscribedTopics.has(t))

    if (newTopics.length === 0) { return }

    return new Promise((resolve, reject) => {
      this.client!.subscribe(newTopics, { qos: 2 }, (err, granted) => {
        if (err) {
          console.error("Subscribe error", err)
          reject(err)
        } else {
          newTopics.forEach(t => this.subscribedTopics.add(t))
          console.log("Subscribed to topics:", granted)
          resolve()
        }
      })
    })
  }

  public async unsubscribe(topics: string | string[]): Promise<void> {
    if (!this.client) { throw new Error("MQTT client not initialized") }

    const topicArray = Array.isArray(topics) ? topics : [topics]
    const existingTopics = topicArray.filter(t => this.subscribedTopics.has(t))

    if (existingTopics.length === 0) { return }

    return new Promise((resolve, reject) => {
      this.client!.unsubscribe(existingTopics, { qos: 2 }, (err) => {
        if (err) {
          console.error("Unsubscribe error", err)
          reject(err)
        } else {
          existingTopics.forEach(t => this.subscribedTopics.delete(t))
          console.log("Unsubscribed from topics:", existingTopics)
          resolve()
        }
      })
    })
  }

  public end(): void {
    if (this.client) {
      try {
        this.client.end(true)
        this.subscribedTopics.clear()
      } catch (err) {
        console.error("Error ending connection", err)
      } finally {
        this.client = null
      }
    }
  }

  public isConnected(): boolean {
    return this.client?.connected || false
  }

  public async publish(topic: string, msg: any): Promise<void> {
    if (!this.client) { throw new Error("MQTT client not initialized") }

    return new Promise((resolve, reject) => {
      this.client!.publish(
        topic,
        JSON.stringify(msg),
        { qos: 1, retain: false },
        (err) => {
          if (err) {
            console.error("Publish error", err)
            reject(err)
          } else {
            resolve()
          }
        }
      )
    })
  }
}

export default MqttHandler
